package com.one.blocks.security.authc;

import com.one.blocks.security.enums.SecurityExceptionEnum;
import com.one.blocks.security.exception.AuthcException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;

/**
 * @author <a href="mailto:idler41@163.con">linfuxin</a> created on 2023-09-23 10:12:19
 */
@Slf4j
public abstract class AbstractAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsChecker preAuthenticationChecks;
    private final UserDetailsChecker postAuthenticationChecks;

    public AbstractAuthenticationProvider() {
        this.preAuthenticationChecks = new DefaultPostAuthenticationChecks();
        this.postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    }

    public AbstractAuthenticationProvider(UserDetailsChecker preAuthenticationChecks, UserDetailsChecker postAuthenticationChecks) {
        this.preAuthenticationChecks = preAuthenticationChecks == null ? new DefaultPostAuthenticationChecks() : preAuthenticationChecks;
        this.postAuthenticationChecks = postAuthenticationChecks == null ? new DefaultPostAuthenticationChecks() : postAuthenticationChecks;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UserDetails userDetails = retrieveUser(authentication);
        if (userDetails == null) {
            throw new AuthcException(SecurityExceptionEnum.NO_ACCOUNT);
        }

        preAuthenticationChecks.check(userDetails);
        additionalAuthenticationChecks(userDetails, authentication);
        postAuthenticationChecks.check(userDetails);

        return buildSuccessAuthentication(authentication, userDetails);
    }

    /**
     * 通过token加载用户认证信息
     *
     * @param authentication token
     * @return 用户认证信息
     */
    protected abstract UserDetails retrieveUser(Authentication authentication);

    /**
     * 认证成功后填充用户详情信息
     *
     * @param authentication 用户认证信息
     * @param userDetails    用户详情
     * @return 用户详情信息
     */
    protected abstract Authentication buildSuccessAuthentication(Authentication authentication, UserDetails userDetails);

    protected abstract void additionalAuthenticationChecks(UserDetails loadedUser, Authentication authentication);

    private static class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                if (log.isDebugEnabled()) {
                    log.debug("account is locked!");
                }
                throw new AuthcException(SecurityExceptionEnum.LOCKED_ACCOUNT);
            }

            if (!user.isEnabled()) {
                if (log.isDebugEnabled()) {
                    log.debug("account is disabled");
                }
                throw new AuthcException(SecurityExceptionEnum.DISABLED_ACCOUNT);
            }

            if (!user.isAccountNonExpired()) {
                if (log.isDebugEnabled()) {
                    log.debug("account is expired");
                }
                throw new AuthcException(SecurityExceptionEnum.EXPIRED_ACCOUNT);
            }
        }
    }

    private static class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        @Override
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                if (log.isDebugEnabled()) {
                    log.debug("account credentials have expired");
                }
                throw new AuthcException(SecurityExceptionEnum.EXPIRED_CREDENTIALS);
            }
        }
    }
}
